from typing import Union, Any, Dict, List, Optional
import logging

import numpy as np
from deepface import DeepFace

'''
By CanYue
'''

self_logger = logging.getLogger("deepface_connector")
logging.basicConfig(format='%(levelname)s - %(name)s - %(message)s - 来自函数: %(funcName)s')


class Connector:
    """
    deepface转接件：对内部方法进行封装，方便deepface的使用
    """

    # 初始化
    def __int__(self, face_db_path: str = ""):
        self.face_db_path = face_db_path
        self.attributes_analyze = ("emotion", "age", "gender")

    def facial_attribute_analysis(
            self,
            img_path: Union[str, np.ndarray],
            actions: Union[tuple, list] = ("emotion", "age", "gender"),
            enforce_detection: bool = True,
            detector_backend: str = "opencv",
            align: bool = True,
            expand_percentage: int = 0,
            silent: bool = False
    ):
        """
        面部属性分析 \n
        注意！本连接器中的方法，在分析完成后返回的是Attribute的实例化对象
        Args:
            img_path (str or np.ndarray): 图像的确切路径，BGR格式的numpy数组，
                或base64编码的图像。如果源图像包含多个面，则结果将包括每个检测到的脸的信息。
            actions: 可选择('age', 'gender', 'race', 'emotion'),本连接器默认不包含'race'
            enforce_detection: 如果在图像中没有检测到人脸，则是否引发异常。
            detector_backend: 人脸检测器后端实现，可选'opencv', 'retinaface',
                'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8' (默认 opencv).
            align (boolean):是否按照人眼位置进行对其 (默认 True)
            expand_percentage: 按百分比扩大检测到的面部面积 (默认 0)
            silent: 是否减少一部分日志输出
        """

        self.attributes_analyze = actions

        facial_attribute: List[Dict[str, Any]] = DeepFace.analyze(
            img_path=img_path,
            actions=actions,
            enforce_detection=enforce_detection,
            detector_backend=detector_backend,
            align=align,
            expand_percentage=expand_percentage,
            silent=silent
        )
        return Attribute(facial_attribute)

    def best_match_by_db(
            self,
            img_path: Union[str, np.ndarray],
            db_path: str,
            model_name: str = "VGG-Face",
            distance_metric: str = "cosine",
            enforce_detection: bool = True,
            detector_backend: str = "opencv",
            align: bool = True,
            expand_percentage: int = 0,
            threshold: Optional[float] = None,
            normalization: str = "base",
            silent: bool = False,
            db_key_is_filename: bool = True,
            file_suffix: bool = False
    ) -> str | list[str] | list[Any]:
        """
        在人脸数据库中，匹配人脸数据,并返回每张图片在数据库中最为匹配的图片文件名 \n
        注意！本方法只返回文件名，不返回文件路径
        :param img_path: 带匹配的图像的确切路径，也可以是BGR格式的numpy数组，或base64编码的图像。
            如果源图像包含多个面，则结果将包括每个检测到的脸的信息。
        :param db_path: 包含映像文件的文件夹的路径。在决策过程中将考虑数据库中所有检测到的人脸。
        :param model_name: 人脸识别模型。可选:VGG-Face, Facenet, Facenet512, OpenFace,
            DeepFace, DeepID, Dlib, ArcFace和SFace(默认为VGG-Face)。
        :param distance_metric: 度量相似性的度量标准。可选:'cosine'， 'euclidean'， 'euclidean_l2'(默认为cos)。
        :param enforce_detection: 如果在图像中没有检测到人脸，是否引发异常
        :param detector_backend: 人脸检测器后端。选项:'opencv'， ' retinface '，'mtcnn'， 'ssd'， 'dlib'，
            'mediapipe'， 'yolov8'(默认为opencv)。
        :param align: 是否根据眼睛位置进行对齐
        :param expand_percentage: 按百分比扩大检测到的面部面积 (默认 0)
        :param threshold: 【机翻】指定一个阈值来确定一对是代表同一个人还是不同的个体。这个阈值用于比较距离。如果不设置，默认的预调优阈值将基于指定的模型名称和距离度量(默认为None)
        :param normalization: 在将输入图像馈送到模型之前对其进行归一化。
            可选:base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace(默认为base)。
        :param silent: 是否减少一部分日志输出
        :param db_key_is_filename: 数据库中图片的命名方式-
            (True 默认)图片名称用于表示用户，例如：db/xiaoming.jpg ; db/xiaohong.jpg |
            (False)同一个人的图片被放置在一个文件夹，文件夹名用于表示用户，例如 db/xiaoming/neutral.jpg ; db/xiaoming/glasses.jpg
        :param file_suffix: 结果文件名是否带后缀,当db_key_is_filename为False时，本参数无效
        :return: 在数据库中匹最为匹配的图片的文件名，若事先传入的图片包含多个面，将以返回列表
        """

        match_the_picture_list = DeepFace.find(
            img_path=img_path,
            db_path=db_path,
            model_name=model_name,
            distance_metric=distance_metric,
            enforce_detection=enforce_detection,
            detector_backend=detector_backend,
            align=align,
            expand_percentage=expand_percentage,
            threshold=threshold,
            normalization=normalization,
            silent=silent
        )
        if db_key_is_filename:
            if file_suffix:
                result = [self.__depath(path["identity"][0]) for path in match_the_picture_list]
            else:
                result = [self.__desuffix(self.__depath(path["identity"][0])) for path in match_the_picture_list]
        else:
            result = [self.__get_folder_name(path["identity"][0]) for path in match_the_picture_list]

        if len(result) == 1:
            return result[0]
        elif len(result) > 1:
            return result
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []  # 错误数据

    def verify_face(
            self,
            source_img_path: Union[str, np.ndarray],
            target_img_path: Union[str, np.ndarray],
            model_name: str = "VGG-Face",
            detector_backend: str = "opencv",
            distance_metric: str = "cosine",
            enforce_detection: bool = True,
            align: bool = True,
            expand_percentage: int = 0,
            normalization: str = "base",
    ) -> bool | List[bool]:
        """
        验证两张照片是否为同一个人
        :param source_img_path: 待对比的照片的确切路径，也可以是BGR格式的numpy数组，或base64编码的图像。
        :param target_img_path: 被对比的照片的确切路径，也可以是BGR格式的numpy数组，或base64编码的图像。
        :param model_name: 人脸识别模型。可选:VGG-Face, Facenet, Facenet512, OpenFace,
            DeepFace, DeepID, Dlib, ArcFace和SFace(默认为VGG-Face)。
        :param detector_backend: 人脸检测器后端。选项:'opencv'， ' retinface '，'mtcnn'， 'ssd'， 'dlib'，
            'mediapipe'， 'yolov8'(默认为opencv)。
        :param distance_metric: 度量相似性的度量标准。可选:'cosine'， 'euclidean'， 'euclidean_l2'(默认为cos)
        :param enforce_detection: 如果在图像中没有检测到人脸，是否引发异常
        :param align: 是否根据眼睛位置进行对齐
        :param expand_percentage: 按百分比扩大检测到的面部面积 (默认 0)
        :param normalization: 在将输入图像馈送到模型之前对其进行归一化。
            可选:base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace(默认为base)。
        :return: 预测两种图片是否是一个人，表同一个人(True)或不同的人(False)。
        """
        result: dict = DeepFace.verify(
            img1_path=source_img_path,
            img2_path=target_img_path,
            model_name=model_name,
            detector_backend=detector_backend,
            distance_metric=distance_metric,
            enforce_detection=enforce_detection,
            align=align,
            expand_percentage=expand_percentage,
            normalization=normalization
        )
        return result["verified"]

    @staticmethod
    def __get_folder_name(file_path: str):
        return file_path.split("\\")[-2]

    @staticmethod
    def __desuffix(file_name: str):
        return file_name.split(".")[0]

    @staticmethod
    def __depath(file_path: str):
        return file_path.split("\\")[-1]


class Attribute:
    def __init__(self,facial_attribute:List):
        self.facial_attribute = facial_attribute

    def get_emotion_score(self) -> Union[Dict[str, str], List[Dict[str, str]]]:
        """
        返回选中图片的各种情绪的分值
        :return: 分值，默认返回字典，若事先传入的图片包含多个面，将以返回列表
        """
        emotion_data = [d["emotion"] for d in self.facial_attribute]
        if len(emotion_data) == 1:
            return emotion_data[0]
        elif len(emotion_data) > 1:
            return emotion_data
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []

    def get_emotion(self) -> Union[str, List]:
        """
        返回选中图片的分别最有可能的情绪
        :return: 最有可能的情绪，默认返回单个字符串，若事先传入的图片包含多个面，将以返回列表
        """
        emotion_list = [d["dominant_emotion"] for d in self.facial_attribute]
        if len(emotion_list) == 1:
            return emotion_list[0]
        elif len(emotion_list) > 1:
            return emotion_list
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return ""

    def get_age(self) -> Union[float, List[float]]:
        """
        返回选中图片预计的年龄
        :return: 预计的年龄，默认返回单个浮点数，若事先传入的图片包含多个面，将以返回列表
        """
        age_list = [d["age"] for d in self.facial_attribute]
        if len(age_list) == 1:
            return age_list[0]
        elif len(age_list) > 1:
            return age_list
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return -1  # 错误数据

    def get_gender_score(self) -> Union[Dict[str, str], List[Dict[str, str]]]:
        """
        返回选中图片各性别分值
        :return: 字典形式返回各性别分值，若事先传入的图片包含多个面，将以返回列表
        """
        gender_data = [d["gender"] for d in self.facial_attribute]
        if len(gender_data) == 1:
            return gender_data[0]
        elif len(gender_data) > 1:
            return gender_data
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []  # 错误数据

    def get_gender(self) -> Union[str, List]:
        """
        返回选中图片最有可能的性别
        :return: 返回各最有可能的性别，若事先传入的图片包含多个面，将以返回列表
        """
        gender_list = [d["dominant_gender"] for d in self.facial_attribute]
        if len(gender_list) == 1:
            return gender_list[0]
        elif len(gender_list) > 1:
            return gender_list
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []  # 错误数据

    def get_race_score(self) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        """
        返回选中图片各人种分值
        :return: 字典形式返回各人种分值，若事先传入的图片包含多个面，将以返回列表
        """
        race_data = [d["race"] for d in self.facial_attribute]
        if len(race_data) == 1:
            return race_data[0]
        elif len(race_data) > 1:
            return race_data
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []  # 错误数据

    def get_race(self) -> Union[str, List]:
        """
        返回选中图片最有可能的人种
        :return: 返回各最有可能的人种，若事先传入的图片包含多个面，将以返回列表
        """
        race_list = [d["dominant_race"] for d in self.facial_attribute]
        if len(race_list) == 1:
            return race_list[0]
        elif len(race_list) > 1:
            return race_list
        else:
            self_logger.warning("deepface返回空数据，请检查输入参数！")
            return []  # 错误数据

class Translater:
    """
    用于翻译DeepFace中输出的英文结果
    """

    def to_chinese(self, text: str) -> str:
        chinese_map = {
            "Man": "男性",
            "Woman": "女性",
            "sad": "伤心",
            "angry": "愤怒",
            "surprise": "惊讶",
            "fear": "畏惧",
            "happy": "高兴",
            "disgust": "厌恶",
            "neutral": "中立",
            "indian": "印第安裔",
            "asian": "亚洲裔",
            "latino hispanic": "拉丁美洲裔",
            "black": "黑种人",
            "middle eastern": "中东裔",
            "white": "白种人"
        }
        if text not in chinese_map.keys():
            self_logger.warning("未找到'{}'的译义".format(text))
            return ""
        return chinese_map[text]


if __name__ == "__main__":
    print("Hello DeepFace!!   :)")
